﻿//AcademicPlanner - course registration planning web app.
//Copyright (C) 2009  Boubacar Diallo

//This program is free software; you can redistribute it and/or
//modify it under the terms of the GNU General Public License
//as published by the Free Software Foundation; either version 2
//of the License, or (at your option) any later version.

//This program is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//GNU General Public License for more details.

//You should have received a copy of the GNU General Public License
//along with this program; if not, write to the Free Software
//Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.


using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using NHibernate;

namespace AcademicPlanner.Data
{
	/// <summary>
	/// EntityRepository is the base class for repositories of data entities of
	/// the AcademicPlanner application. An EntityRepository provides means to
	/// retrieve and persist <c>Entity</c>s. It relies on NHibernate for persistence
	/// management.
	/// </summary>
	/// <seealso href="https://www.hibernate.org/343.html"/>
	/// <since>1.0</since>
	public abstract class EntityRepository
	{
		internal abstract string DBTableName { get; }

		/// <summary>
		/// The prefix for this EntityRepository to be used when retrieving queries
		/// defined in NHibernate mappings.
		/// </summary>
		/// <since>1.0</since>
		protected string namedQueriesPrefix = "";


		/// <summary>
		/// Gets the <c>DataSourceAccessSupervisor</c> of the data layer of the application.
		/// </summary>
		/// <since>1.0</since>
		protected DataSourceAccessSupervisor DSAS { get { return DataSourceAccessSupervisor.GetInstance(); } }

		/// <summary>
		/// Gets the prefix to be used when retrieving named queries for this EntityRepository.
		/// </summary>
		/// <since>1.0</since>
		protected virtual string NamedQueriesPrefix { get { return namedQueriesPrefix; } }


		/// <summary>
		/// Gets a named query defined in the NHibernate mapping of this EntityRepository's <c>Entity</c>.
		/// This method requests the query from the current NHibernate session.
		/// </summary>
		/// <param name="pName">
		/// The name of the query; this is not the name as defined in the NHibernate mapping.
		/// The name defined in the NHibernate mapping is in the format
		///		<c>NamedQueriesPrefix +"."+<paramref name="pName">name of the query</paramref></c>.
		/// </param>
		/// <returns>The query object.</returns>
		/// <exception cref="T:DataException">
		/// Thrown with an error code of <c>DataErrorCode.GET_NAMED_QUERY_FAILED</c>
		/// when an error is reported by NHibernate. The <c>HibernateException</c> is
		/// accessible via the <c>InnerException</c> property.
		/// </exception>
		/// <since>1.0</since>
		protected IQuery GetNamedQuery(string pName)
		{
			string vFullName = NamedQueriesPrefix + "." + pName;
			IQuery rQuery = null;

			try
			{
				rQuery = DSAS.Session.GetNamedQuery(vFullName);
			}
			catch (HibernateException vHx)
			{
				var vDx = new DataException(DataErrorCode.GET_NAMED_QUERY_FAILED, vHx);
				throw vDx;
			}

			return rQuery;
		}

		/// <summary>
		/// Gets a new NHibernate <c>ISQLQuery</c>.
		/// </summary>
		/// <param name="pSql">The sql query.</param>
		/// <returns>the NHibernate <c>ISQLQuery</c>.</returns>
		/// <exception cref="T:DataException">
		/// Thrown with an error code of <c>DataErrorCode.CREATE_SQL_QUERY_FAILED</c>
		/// when an error is reported by NHibernate. The <c>HibernateException</c> is
		/// accessible via the <c>InnerException</c> property.
		/// </exception>
		/// <since>1.0</since>
		protected ISQLQuery GetSqlQuery(string pSql)
		{
			ISQLQuery rQuery = null;

			try
			{
				rQuery = DSAS.Session.CreateSQLQuery(pSql);
			}
			catch (HibernateException vHx)
			{
				var vDx = new DataException(DataErrorCode.CREATE_SQL_QUERY_FAILED, vHx);
				throw vDx;
			}

			return rQuery;
		}

		/// <summary>
		/// Gets a new NHibernate <c>IQuery</c> for filtering the given collection.
		/// </summary>
		/// <param name="pCollection">The collection to filter.</param>
		/// <param name="pFilterQuery">The filter query.</param>
		/// <returns>The <c>IQuery</c> for filtering.</returns>
		/// <exception cref="T:DataException">
		/// Thrown with an error code of <c>DataErrorCode.CREATE_FILTER_QUERY_FAILED</c>
		/// when an error is reported by NHibernate. The <c>HibernateException</c> is
		/// accessible via the <c>InnerException</c> property.
		/// </exception>
		/// <since>1.0</since>
		protected IQuery GetFilterQuery(ICollection pCollection, string pFilterQuery)
		{
			IQuery rQuery = null;

			try
			{
				rQuery = DSAS.Session.CreateFilter(pCollection, pFilterQuery);
			}
			catch (HibernateException vHx)
			{
				var vDx = new DataException(DataErrorCode.CREATE_FILTER_QUERY_FAILED, vHx);
				throw vDx;
			}

			return rQuery;
		}

		/// <summary>
		/// Saves a <c>Entity</c> to the data storage.
		/// </summary>
		/// <param name="pEntity">The <c>Entity</c> to save.</param>
		/// <exception cref="T:DataException">
		/// Thrown with an error code of <c>DataErrorCode.SAVE_ENTITY_FAILED</c>
		/// when an error is reported by NHibernate. The <c>HibernateException</c> is
		/// accessible via the <c>InnerException</c> property.
		/// </exception>
		/// <since>1.0</since>
		public virtual void Save(Entity pEntity)
		{
			try
			{
				if (pEntity.Id <= 0)
					DSAS.Session.SaveOrUpdate(pEntity);
			}
			catch (HibernateException vHx)
			{
				var vDx = new DataException(DataErrorCode.SAVE_ENTITY_FAILED, vHx);
				throw vDx;
			}
		}

		/// <summary>
		/// Reloads an <c>Entity</c> from the data storage.
		/// </summary>
		/// <param name="pEntity">The <c>Entity</c> to reload.</param>
		/// <exception cref="T:DataException">
		/// Thrown with an error code of <c>DataErrorCode.REFRESH_ENTITY_FAILED</c>
		/// when an error is reported by NHibernate. The <c>HibernateException</c> is
		/// accessible via the <c>InnerException</c> property.
		/// </exception>
		/// <since>1.0</since>
		public virtual void Reload(Entity pEntity)
		{
			try
			{
				if (pEntity.Id > 0)
					DSAS.Session.Refresh(pEntity);
			}
			catch (HibernateException vHx)
			{
				var vDx = new DataException(DataErrorCode.REFRESH_ENTITY_FAILED, vHx);
				throw vDx;
			}
		}
	}

	/// <summary>
	/// EntityRepository(TEntity) brings strong typing to <c>EntityRepository</c>s.
	/// It encapsulates all strongly typed but common operations to <c>EntityRepository</c>s.
	/// </summary>
	/// <typeparam name="TEntity">The type of the <c>Entity</c> reposited.</typeparam>
	/// <since>1.0</since>
	public abstract class EntityRepository<TEntity> : EntityRepository where TEntity : Entity
	{
		/// <summary>
		/// Finds this <c>EntityRepository</c>'s <c>Entity</c> matching the given identifier
		/// from the data storage.
		/// </summary>
		/// <param name="pId">The <c>Entity</c> identifier.</param>
		/// <returns>The <c>Entity</c> having the given identifier or <c>null</c>.</returns>
		/// <exception cref="T:DataException">
		/// Thrown with an error code of <c>DataErrorCode.GET_ENTITY_FAILED</c>
		/// when an error is reported by NHibernate. The <c>HibernateException</c> is
		/// accessible via the <c>InnerException</c> property.
		/// </exception>
		/// <since>1.0</since>
		public virtual TEntity FindById(int pId)
		{
			TEntity rEnt = null;

			try
			{
				rEnt = DSAS.Session.Get<TEntity>(pId);
			}
			catch (HibernateException vHx)
			{
				var vDx = new DataException(DataErrorCode.GET_ENTITY_FAILED, vHx);
				throw vDx;
			}

			return rEnt;
		}

		/// <summary>
		/// Filters a collection of this EntiyRepository's <c>Entity</c>s and
		/// returns the one with the given id.
		/// </summary>
		/// <remarks>
		/// This method calls <see cref="M:GetFilterQuery"/> to get an NHibernate
		/// <c>IQuery</c> for filtering.
		/// </remarks>
		/// <param name="pCollection">The collection to filter.</param>
		/// <param name="pId">The id.</param>
		/// <returns>The <c>Entity</c> in the collection with the given id.</returns>
		/// <exception cref="T:DataException">
		/// Thrown with an error code of <c>DataErrorCode.FILTER_ENTITY_BY_ID_FAILED</c>
		/// when an error is reported by NHibernate. The <c>HibernateException</c> is
		/// accessible via the <c>InnerException</c> property.
		/// </exception>
		/// <since>1.0</since>
		public virtual TEntity FilterById(ICollection pCollection, int pId)
		{
			TEntity rEnt = null;
			var vFilter = "where Id = :id";
			var vQuery = GetFilterQuery(pCollection, vFilter);

			try
			{
				vQuery.SetInt32("id", pId);
				DSAS.DisableFilters();
				rEnt = vQuery.UniqueResult<TEntity>();
				DSAS.EnableFilters();
			}
			catch (HibernateException vHx)
			{
				var vDx = new DataException(DataErrorCode.FILTER_ENTITY_BY_ID_FAILED, vHx);
				throw vDx;
			}

			return rEnt;
		}

		/// <summary>
		/// Creates a new instance of this <c>EntityRepository</c>'s <c>Entity</c>
		/// with default values.
		/// </summary>
		/// <returns>The new <c>Entity</c>.</returns>
		/// <seealso cref="M:Entity.SetDefaults"/>
		/// <since>1.0</since>
		public virtual TEntity GetNew()
		{
			var rEnt = Activator.CreateInstance<TEntity>();
			rEnt.SetDefaults();
			return rEnt;
		}
	}
}
